#include "ym2612/gensplus-gx/ym2612.h"
#include "ym2612/ym2612.h"
#include "YM2612Chip.h"		

//-------------------------------------------------------------------------------------------------------
//YM2612Chip - Wrapper for a YM2612 implementation
//-------------------------------------------------------------------------------------------------------
YM2612Chip::YM2612Chip()
	:_mode(MODE_GXPLUS)
	,_impl(nullptr)
	,_implGens(nullptr)
	,_leftoverSamples(0)
	,_finishFrame(0)
{
}

//-------------------------------------------------------------------------
	
YM2612Chip::~YM2612Chip()
{
}

//-------------------------------------------------------------------------

void YM2612Chip::Initialize( YM2612Clock clock, int soundRate, int improv )
{
	//Initialize the resampler to resample at 44100hz
	_resampler.Fir_Resampler_initialize(4096);
	_fmCyclesRatio = 144 * 7 * (1<<11);
	_resampler.Fir_Resampler_time_ratio( 53693175 / (double)44100 / (144.0 * 7.0), 0.995 );

	_impl = new I2612Impl();
	
	_impl->YM2612Init( clock, soundRate );
	_impl->YM2612Restore( (unsigned char*)&_impl->ym2612 );
	_impl->YM2612ResetChip();

	_implGens = new I2612Impl_Gens();
	_implGens->YM2612_Init( clock, soundRate, improv );
}

//-------------------------------------------------------------------------

void YM2612Chip::Terminate()
{
	if( _impl != nullptr )
		delete _impl;
	if( _implGens != nullptr )
		delete _implGens;
}

//-------------------------------------------------------------------------

//This function actually updates the state of the YM2612 emulation, and
//returns the requested number of samples(length). Output will sound different
//depending on wether you're using GENS or GXPLUS emulation. GXPLUS is more
//accurate, but GENS is capable of generating an interesting sound that you may
//prefer.
void YM2612Chip::Update( float **buf, int length )
{
	float* out1 = buf[0];
	float* out2 = buf[1];
	if( _mode == MODE_GENS )
	{
		//Gens is simple, we just update the chip and take the output.
		int index = 0;
		int* vals[2];
		int left, right;
		left = right = 0;
		vals[0] = &left;
		vals[1] = &right;

		while( index < length )
		{
			_implGens->YM2612_Update(vals, 1);
			out1[index] += (float)(left / (float)32767);
			out2[index] += (float)(right / (float)32767);
			index++;
		}
	}
	else if( _mode == MODE_GXPLUS )
	{
		//GXPlus is a little harder, we need to resample the output we generate from 53267hz to 44100hz
		int* buffer = _resampler.Fir_Resampler_buffer();
		int reqFrames = length;
		int curFrame = 0;

		//If we have any frames left from the previous resample batch, add them to the output
		if( _leftoverSamples > 0 )
		{
			for( int i = 0; i < _leftoverSamples; i += 2 )
			{
				out1[curFrame] += (float)(_samples[_finishFrame + i] / (float)32767);
				out2[curFrame] += (float)(_samples[_finishFrame + i+1] / (float)32767);		
				curFrame++;
				reqFrames--;
			}
		}
		_finishFrame = 0;
		_leftoverSamples = 0;

		//Update the 2612 until we have enough frames to continue
		int avail = 0;	
		do
		{
			_impl->YM2612Update(_resampler.Fir_Resampler_buffer(), 1);
			_resampler.Fir_Resampler_write( 2 );
		
			avail = _resampler.Fir_Resampler_avail();
		} while( avail < reqFrames );
		int num = _resampler.Fir_Resampler_read( _samples, avail );

		//Add the generated frames to the current output buffer
		for( int i = 0; i < reqFrames * 2; i+=2 )
		{
			out1[curFrame] += (float)(_samples[i] / (float)32767);
			out2[curFrame] += (float)(_samples[i+1] / (float)32767);
			curFrame++;
			avail -= 2;
		}

		//Carry over anything we miss
		if( avail > 0 )
		{
			_finishFrame = curFrame * 2;
			_leftoverSamples = avail;
		}
	}
}

//-------------------------------------------------------------------------

//Writes a parameter and a value to the chip, changing it's state.
void YM2612Chip::SetParam( YM2612Param param, int op, unsigned char val, int channel )
{
	int reg = GetRegister( param, op, channel % 3 );
	if( channel <= 2 )
		WritePort0(reg, val);
	else
		WritePort1(reg, val);
}

//-------------------------------------------------------------------------

void YM2612Chip::SetAlgorithm( int alg, int channel )
{
	SetParam( FB_ALGORITHM, 0, alg, channel );
}

//-------------------------------------------------------------------------

//Simply writes a value into a register. Port 0 handles 
//channels 1 - 3; port 1 handles channels 4 - 6.
void YM2612Chip::WritePort0( int reg, int val )
{
	_implGens->YM2612_Write(0, reg);
	_implGens->YM2612_Write(1, val);	
	_impl->YM2612Write(0, reg);
	_impl->YM2612Write(1, val);	
}
void YM2612Chip::WritePort1( int reg, int val )
{
	_implGens->YM2612_Write(2, reg);
	_implGens->YM2612_Write(3, val);	
	_impl->YM2612Write(2, reg);
	_impl->YM2612Write(3, val);	
}

//-------------------------------------------------------------------------

//Writes all information relevant to a channel to the chip.
void YM2612Chip::WriteChannel( YM2612ChannelData data, int channel )
{
	//FB_ALGORITHM
	//------------------------------
	//  0   1   2   3   4   5   6   7
	//          |   FB  |   |  ALG  |
	unsigned char fb = data.Feedback.GetValue();
	unsigned char alg = (unsigned char)data.Algorithm.GetAlgSelection();
	unsigned char fbalg = (fb << 3) | alg;
	SetParam( FB_ALGORITHM, 0, fbalg, channel );

	WriteOperator( data.Operators[0], 0, channel );
	WriteOperator( data.Operators[1], 1, channel );
	WriteOperator( data.Operators[2], 2, channel );
	WriteOperator( data.Operators[3], 3, channel );
}

//-------------------------------------------------------------------------

//Writes all information relevant to an operator to the chip.
void YM2612Chip::WriteOperator( YM2612Operator data, int op, int channel )
{
	//DT_MUL
	//------------------------------
	//  0   1   2   3   4   5   6   7
	//      |  DT   |   |    MUL    |
	unsigned char mul = data.P_MUL.GetValue();
	unsigned char dt = data.P_DT.GetValue();
	unsigned char dtmul = (dt << 4) | mul;
	SetParam( DT_MUL, op, dtmul, channel );

	//TL
	SetParam( data.P_TL.GetParam(), op, data.P_TL.GetValue(), channel );

	//RS_AR
	//------------------------------
	//  0   1   2   3   4   5   6   7
	//  |RS |  |X|  |      AR       |
	unsigned char rs = data.P_RS.GetValue();
	unsigned char ar = data.P_AR.GetValue();
	unsigned char rsar = (rs << 6) | ar;
	SetParam( RS_AR, op, rsar, channel );

	//AM_DR1
	//------------------------------
	//  0   1   2   3   4   5   6   7
	//|AM|          |      DR1      |
	unsigned char am = data.P_AM.GetValue();
	unsigned char dr1 = data.P_DR1.GetValue();
	unsigned char amdr1 = (am << 7) | dr1;
	SetParam( AM_DR1, op, amdr1, channel );
	
	//DR2
	SetParam( data.P_DR2.GetParam(), op, data.P_DR2.GetValue(), channel );

	//DL1_RR
	//------------------------------
	//  0   1   2   3   4   5   6   7
	//  |    DL1    |   |     RR    |
	unsigned char dl1 = data.P_DL1.GetValue();
	unsigned char rr = data.P_RR.GetValue();
	unsigned char dl1rr = (dl1 << 4) | rr;
	SetParam( DL1_RR, op, dl1rr, channel );
	
	//F2
	SetParam( data.P_F2.GetParam(), op, data.P_F2.GetValue(), channel );
	//F1
	SetParam( data.P_F1.GetParam(), op, data.P_F1.GetValue(), channel );
}

//-------------------------------------------------------------------------

void YM2612Chip::ClearNote( int channel )
{
	SetParam(NOTE_ON, 0, channel > 2 ? (channel + 1) : channel, channel % 3);
}

//-------------------------------------------------------------------------

void YM2612Chip::FireNote( int channel )
{
	SetParam(NOTE_ON, 0, 0xF0 | (channel > 2 ? (channel + 1) : channel), channel % 3);
}

//-------------------------------------------------------------------------

//Returns the register corresponding to a specific parameter. Some registers
//may be multi purpose registers, in which case it's important that you pack
//your data in a relevant way to ensure you're modifying the right parameter.
int YM2612Chip::GetRegister( YM2612Param param, int op, int channel )
{
	switch( param )
	{
	case DT:
	case MUL:
	case DT_MUL:
		return (48 + channel) + (op * 4);
		break;
	case TL:
		return (64 + channel) + (op * 4);
		break;
	case RS:
	case AR:
	case RS_AR:
		return (80 + channel) + (op * 4);
		break;
	case AM:
	case DR1:
	case AM_DR1:
		return (96 + channel) + (op * 4);
		break;
	case DR2:
		return (112 + channel) + (op * 4);
		break;
	case DL1:
	case RR:
	case DL1_RR:
		return (128 + channel) + (op * 4);
		break;
	case SSG_EG:
		return (144 + channel) + (op * 4);
		break;
	case FNUM_1:
		return 160 + channel;
		break;
	case FNUM_2:
		return 164 + channel;
		break;
	case FB_ALGORITHM:
		return 176 + channel;
		break;
	case NOTE_ON:
		return 0x28;
		break;
	case SPECIAL_MODE:
		return 0x27;
		break;
	case F1:
		return 0xA9 - op;
		break;
	case F2:
		return 0xAD - op;
		break;
	}

//-------------------------------------------------------------------------

}